﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;

using LeanEngine.Extensions;
using LeanEngine.Entity;
using LeanEngine.Collections;
using LeanEngine.Data;

namespace LeanEngine.Business
{
    /// <summary>
    /// Represents a set of business method for all basic operations.
    /// </summary>
    /// <typeparam name="T">The entity type to be used.</typeparam>
    public class BusinessCrud<T>
        where T : PlainEntity, new()
    {
        #region TransferObject Method Members

        /// <summary>
        /// Creates a new row in the database whether all data validations are valid.
        /// </summary>
        /// <param name="value">The data transfer object instance that holds all data to insert in the database.</param>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="System.Data.DataException"></exception>
        /// <exception cref="LeanEngine.BusinessException"></exception>
        public virtual T Insert(T value)
        {
            value.Insert();
            return value;
        }

        /// <summary>
        /// Updates a row in the database whether all data validations are valid.
        /// </summary>
        /// <param name="value">The data transfer object instance that holds all data to update in the database.</param>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="System.Data.DataException"></exception>
        /// <exception cref="LeanEngine.BusinessException"></exception>
        public virtual T Update(T value)
        {
            value.Update();
            return value;
        }

        /// <summary>
        /// Reads all information associated with the <typeparamref name="T"/> object.
        /// </summary>
        /// <param name="keys">The list of values representing all primary key values.</param>
        /// <exception cref="System.Data.DataException"></exception>
        public virtual T Read(params object[] keys)
        {
            if (keys.IsNull()) throw new ArgumentNullException("keys");

            if (keys.Length == 0) throw new ArgumentOutOfRangeException("keys");

            var metaTable = MetaTableCache.GetItem(typeof(T));
            if (metaTable.ColumnsList.WherePrimaryKey().Count() == 0)
            {
                throw new System.Data.DataException("This operation doesn't work with entities without primary keys.");
            }

            if (keys.Length != metaTable.ColumnsList.WherePrimaryKey().Count())
            {
                throw new ArgumentOutOfRangeException("keys", "You must informed all primary key values.");
            }

            var value = new T();
            var index = 0;
            foreach (var property in metaTable.ColumnsList.WherePrimaryKey())
            {
                property.SetValue(value, keys[index++]);
            }

            using (var cmd = new CrudCommand())
            {
                return cmd.ExecuteReader(value) as T;
            }
        }

        /// <summary>
        /// Deletes a row from the database according all primary key values of <typeparamref name="T"/> object.
        /// </summary>
        /// <param name="value">The data transfer object instance that holds the primary key values of the row to delete.</param>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="System.Data.DataException"></exception>
        /// <exception cref="LeanEngine.BusinessException"></exception>
        public virtual void Delete(T value)
        {
            value.Delete();
        }

        /// <summary>
        /// Paginates all records in the <typeparamref name="T"/> table and returns the maximum amount of data at the specified page index.
        /// </summary>
        /// <param name="pageIndex">The zero-based index of the page</param>
        /// <param name="pageSize">The maximum amount of data by page.</param>
        /// <exception cref="System.Data.DataException"></exception>
        public virtual PagedList<T> List(int pageIndex, int pageSize)
        {
            using (var cmd = new CrudCommand())
            {
                PagedList<T> list = cmd.ExecuteReader<T>(pageIndex, pageSize);
                if (list.Items.Count.IsGreaterThan(0))
                {
                    list.TotalItems = cmd.ExecuteCount(typeof(T));
                }

                return list;
            }
        }

        /// <summary>
        /// Lists all records in the table <typeparamref name="T"/>.
        /// </summary>
        /// <exception cref="System.Data.DataException"></exception>
        public virtual List<T> ListAll()
        {
            using (var cmd = new CrudCommand())
            {
                PagedList<T> list = cmd.ExecuteReader<T>(0, int.MaxValue);

                return list.Items;
            }
        }

        /// <summary>
        /// Counts the amount of data has in <typeparamref name="T"/> table
        /// </summary>
        /// <exception cref="MissingMemberException"></exception>
        /// <exception cref="System.Data.DataException"></exception>
        public virtual long GetCount()
        {
            using (var cmd = new CrudCommand())
            {
                return cmd.ExecuteCount(typeof(T));
            }
        }

        #endregion
    }
}
